/*
 *  CellularAutomation.cpp
 *  
 *
 *  Created by Daniel Condon on 25/07/12.
 *  Copyright 2012 __MyCompanyName__. All rights reserved.
 *
 */

#include "CellularAutomation.h"
#include <math.h>
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <iostream>

int Round(double roundee){
	int ret;
	int pick = (int)roundee;
	double slide = roundee - pick;
	if (slide < 0.5) {
		ret = floor(roundee);
	}else if (slide >= 0.5) {
		ret = ceil(roundee);
	}
	return ret;
}

/*--Default Constructor
 builds a 1 cell matrix
 */
CellularAutomation::CellularAutomation(){
	width = 1;
	height = 1;
}

/*--Main Constructor
 Builds the array columns->rows
 Params
 Parameter(0) : number of columns in the matrix
 Parameter(1) : number of rows in the matrix
 Parameter(2) : default creature for cellular automata
 */
CellularAutomation::CellularAutomation(int columns, int rows, Creature setCreature){
	width = columns;
	height = rows;
	creatures = setCreature;
	
	r = gsl_rng_alloc (gsl_rng_mt19937);
	gsl_rng_set(r, (unsigned long int)time(NULL));
	
	cell = new Cell**[width];
	for (x = 0; x < width; x++) {
		cell[x] = new Cell*[height];
		for (y = 0; y < height; y++) {
			cell[x][y] = new Cell(x,y,200);
		}
	}	
}

/*--Deconstructor
 Builds the array columns->rows
 Params
 Parameter(0) : number of columns in the matrix
 Parameter(1) : number of rows in the matrix
 Parameter(2) : default creature for cellular automata
 */
CellularAutomation::~CellularAutomation(){
	for (x = 0; x < height; x++) {
		delete [] cell[x];
	}
	delete [] cell;
}

/*--This function will seed a cell with some initial migrants
 Params
 Parameter(0) : x coordinate of the cell
 Parameter(1) : y coordinate of the cell
 Parameter(2) : number of migrants to initiate with
 Pre : Param(0&1) valid number within the array bounds
 Param(2) valid number less than the maximum cell
 */
void CellularAutomation::seedCell(int seedx, int seedy, int seedAmount){
	if (seedx < width && seedy < height) {
		cell[seedx][seedy]->receiveMigrants(seedAmount);
	}
}

/*--This function returns the matrix width
 Returns : matrix max x value
 */
int CellularAutomation::getWidth(){
	return width;
}

/*--This function returns the matrix height
 Returns : matrix max y value
 */
int CellularAutomation::getHeight(){
	return height;
}

/*--Returns the Cell at a given point
 Params:
 Parameter(0) : x coordinate of the cell
 Parameter(1) : y coordinate of the cell
 Pre : Parameter(0&1) not NULL and within the bounds of the matrix
 Returns : Cell at point Parameter(0):Parameter(1)
 */	
Cell* CellularAutomation::getCell(int x, int y){
	return cell[x][y];
}

/*--This function returns the current creatures in the cell
 Params:
 Parameter(0) : x coordinate of the cell
 Parameter(1) : y coordinate of the cell
 Pre : Parameter(0&1) not NULL and within the bounds of the matrix
 Returns : Cell population at point Parameter(0):Parameter(1)
 */	
int CellularAutomation::getCellCurrent(int x, int y){
	return cell[x][y]->getCellCurrent();
}

/*--This function returns the maximum creatures for the cell
 Params:
 Parameter(0) : x coordinate of the cell
 Parameter(1) : y coordinate of the cell
 Pre : Parameter(0&1) not NULL and within the bounds of the matrix
 Returns : Cell population maximum at point Parameter(0):Parameter(1)
 */	
int CellularAutomation::getCellMax(int x, int y){
	return cell[x][y]->getCellMax();
}

/*--This function approximates the barriers and 
 whether the migration event can progress
 Params:
 Parameter(0) : x coordinate of the cell
 Parameter(1) : y coordinate of the cell
 Parameter(2) : degrees from 0(left or side a) the migration event indends to travel
 Pre : Parameter(0&1) not NULL and within the bounds of the matrix
 Returns : If the migration event connects with a wall
 
 NOTE : will need to convert this to actual line calculations
 */	
bool CellularAutomation::Check(int x, int y, double degree){
	bool sides[4];
	sides[0] = cell[x][y]->hasBarrier(0);
	sides[1] = cell[x][y]->hasBarrier(1);
	sides[2] = cell[x][y]->hasBarrier(2);
	sides[3] = cell[x][y]->hasBarrier(3);
	
	bool ret = true;
	
	if (sides[0]) {
		if (degree <= 45.0 || degree > 315.0) {
			ret = false;
		}
	}
	
	if (sides[1]) {
		if (degree <= 135.0 && degree > 45.0) {
			ret = false;
		}
	}
	
	if (sides[2]) {
		if (degree <= 225.0 && degree > 135.0) {
			ret = false;
		}
	}
	
	if (sides[3]) {
		if (degree <= 315.0 && degree > 225.0) {
			ret = false;
		}
	}
	return ret;
}

/*--This function detirmines the direction of the migration event
 Params:
 Parameter(0) : gsl random number generator
 Parameter(1) : x coordinate of the cell
 Parameter(2) : y coordinate of the cell
 Pre : Parameter(0&1) not NULL and within the bounds of the matrix
 Returns : direction in degrees the migration event will occur
 */
double CellularAutomation::DetirmineDirection(gsl_rng * r, int x, int y){
	double degree = 2.0*M_PI*gsl_rng_uniform_pos(r);
	//if hits barrier roll to pass
	//if fails roll, reroll
	degree *= (180/M_PI);
	while (!Check(x,y,degree)) {
		degree = (2.0*M_PI*gsl_rng_uniform_pos(r))*(180/M_PI);
	}
	degree *= (M_PI/180);
	return degree;
}

/*--This function detirmines the distance of the migration event
 Params:
 Parameter(0) : gsl random number generator
 Returns : distance the migration event will travel
 */
double CellularAutomation::MigrationRadius(gsl_rng * r){
	double radius;
	double rndVal = gsl_rng_uniform_pos(r);
	if (rndVal <= (1.0f-creatures.getPldd())) {
		radius = creatures.getPsdd()*2;//gsl_ran_gaussian_tail(r, 1, creatures.getPsdd());
	}else {
		radius = gsl_ran_gaussian_tail(r, 1, 1);
	}
	return radius;
}

/*--This function detirmines the number of migrats
 Params:
 Parameter(0) : gsl random number generator
 Parameter(1) : x coordinate of the cell
 Parameter(2) : y coordinate of the cell
 Pre : Parameter(1&2) not NULL and within the bounds of the matrix
 Returns : number of migrants in the migration event
 */
int CellularAutomation::MigrationSize(gsl_rng * r, int x, int y){
	int migrationSize;
	if (creatures.getPmig() < 0.11 && cell[x][y]->getCellCurrent() > 30.0) {
		migrationSize = (int)gsl_ran_poisson(r, creatures.getPmig()*cell[x][y]->getCellCurrent());
	}else {
		migrationSize = (int)gsl_ran_binomial(r, creatures.getPmig(), cell[x][y]->getCellCurrent());
	}
	if (migrationSize > cell[x][y]->getCellCurrent()) {
		migrationSize = cell[x][y]->getCellCurrent();
	}
	return migrationSize;
}

/*--This function will send migrants for each cell using DetirmineDirection
 MigrationRadius and MigrationSize to detirmine parameters for sending
 */
void CellularAutomation::MigrationCycle(){
	for (x = 0; x < width; x++) {
		for (y = 0; y < height; y++) {
			//migration cycle
			//random degree for migration direction
			if (cell[x][y]->getCellCurrent() > 20 && cell[x][y]->canSpread()) {
				int migrationSize = MigrationSize(r,x,y);
				double degree = DetirmineDirection(r, x, y);
				if (!(degree == -1.0)) {
					double radius = MigrationRadius(r);
					
					double a = radius*cos(degree) + x;
					double b = radius*sin(degree) + y;
					int i_x = Round(a);
					int i_y = Round(b);
					//boundry check
					if (i_x >= 0 && i_y >= 0 && i_x < width && i_y < height) {
						cell[x][y]->sendMigrants(migrationSize);
						cell[i_x][i_y]->receiveMigrants(migrationSize);
					}
				}
			}
		}
	}
}

/*--This function will receive migrants for each cell and add them to the
 current creatures in the cell
 */
void CellularAutomation::CompleteMigration(){
	for (int x = 0; x < width; x++) {
		for (int y = 0; y < height; y++) {
			cell[x][y]->incorporateMigrants();
		}
	}
}

/*--This function will calculate growth within the cell using a poisson distribution
 */
void CellularAutomation::GrowthCycle(){
	for (x = 0; x < width; x++) {
		for (y = 0; y < height; y++) {
			//growth cycle
			double curr = (double)cell[x][y]->getCellCurrent();
			if (cell[x][y]->getCellCurrent() > 0) {
				double max = (double)cell[x][y]->getCellMax();
				double rate = creatures.getGrowth();
				
				double g = max*curr*rate;
				double g1 = max+curr*(rate-1);
				double g2 = curr;
				double growth = (g/g1)-g2;
				growth = gsl_ran_poisson(r, growth);// + cell[x][y]->getCellCurrent();
				
				cell[x][y]->applyGrowth(growth);
			}
		}
	}
}

/*--This function will run the full cycle(MigrationCycle, CompleteMigration and GrowthCycle)
 */
void CellularAutomation::Cycle(){
	MigrationCycle();
	CompleteMigration();
	GrowthCycle();
}